万字Python零基础入门总结
♡Tips:公众号后台回复:python,即可领取零基础学Python教程视频
1、输入输出
输出
print('100 + 200 =', 100 + 200)
print('The quick brown fox', 'jumps over', 'the lazy dog')
The quick brown fox jumps over the lazy dogprint()
会依次打印每个字符串,遇到逗号“,”会输出一个空格print()
在括号中加上字符串,就可以向屏幕上输出指定的文字print()
函数也可以接受多个字符串,用逗号“,”隔开,就可以连成一串输出;print()
也可以打印整数,或者计算结果输入
name = input()
Python提供了一个
input()
,可以让用户输入字符串,并存放到一个变量里
2、缩进和注释
以 #
开头的语句是注释每一行都是一个语句,当语句以冒号 :
结尾时,缩进的语句视为代码块;缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的惯例,「应该始终坚持使用4个空格的缩进」。缩进的另一个好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码。缩进的坏处就是“复制-粘贴”功能失效了,这是最坑爹的地方。当你重构代码时,粘贴过去的代码必须重新检查缩进是否正确。此外,IDE很难像格式化Java代码那样格式化Python代码。 Python程序是「大小写敏感」的,如果写错了大小写,程序会报错
3、数据类型和变量
整数
Python可以处理任意大小的整数,当然包括负整数,在程序中的表示方法和数学上的写法一模一样
dec = int(input("输入数字:"))
print("十进制数为:", dec)
print("转换为二进制为:", bin(dec))
print("转换为八进制为:", oct(dec)) # 0o开头
print("转换为十六进制为", hex(dec)) # 0x开浮点数
int(x [,base ]) 将x转换为一个整数
long(x [,base ]) 将x转换为一个长整数
float(x ) 将x转换到一个浮点数
complex(real [,imag ]) 创建一个复数
str(x ) 将对象 x 转换为字符串
repr(x ) 将对象 x 转换为表达式字符串
eval(str ) 用来计算在字符串中的有效Python表达式,并返回一个对象
tuple(s ) 将序列 s 转换为一个元组
list(s ) 将序列 s 转换为一个列表
chr(x ) 将一个整数转换为一个字符
unichr(x ) 将一个整数转换为Unicode字符
ord(x ) 将一个字符转换为它的整数值
hex(x ) 将一个整数转换为一个十六进制字符串
oct(x ) 将一个整数转换为一个八进制字符串「使用字符串格式化」
「使用round内置函数」
x = 1234.56789
print(format(x, '0.2f'))
1234.57占位符 替换内容 %d 整数 %f 浮点数 %s 字符串 %x 十六进制整数 dec = float(input("输入的数字"))
print("dec*dec =",round(dec*dec,2));https://www.cnblogs.com/qinchao0317/p/10699717.html
保留小数点位数
数值类型转换
4、字符串
字符串是以单引号
'
或双引号"
括起来的任意文本;如果'
本身也是一个字符,那就可以用""
括起来,比如"I'm OK"
包含的字符是I
,'
,m
,空格,O
,K
这6个字符。如果字符串内部既包含'
又包含"
怎么办?可以用转义字符\
来标识;转义字符
\
可以转义很多字符,比如\n
表示换行,\t
表示制表符,字符\
本身也要转义,所以\\
表示的字符就是\
,print('i\nam\nok')
「Python还允许用
r''
表示''
内部的字符串默认不转义」print(r'xxjis\nosk\t')
如果字符串内部有很多换行,用
\n
写在一行里不好阅读,为了简化,Python允许用'''...'''
的格式表示多行内容print('''zzxsx
zxsaxa
sasads
s''')print("i'm okss")
print('i\'m \" ok')
5、布尔值
个布尔值只有
True
、False
两种值,要么是True
,要么是Falseand、 or、 not
print(True and True)
print(not 1>2)
6、空值
None表示空值,它是一个特殊 Python 对象, None的类型是NoneType
print(type(None)) #<class 'NoneType'>
7、变量
变量在程序中就是用一个变量名表示了,变量名必须是大小写英文、数字和
_
的组合,且不能用数字开头「变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。例如Java是静态语言」
int a = 123; // a是整数类型变量
a = "ABC"; // 错误:不能把字符串赋给整型变量
8、常量
所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:
PI = 3.14159265359
但事实上
PI
仍然是一个变量,Python根本没有任何机制保证PI
不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI
的值,也没人能拦住你赋值
a,b,c=1,2,3
a,b=b,a+b
print(a,b)
9、除法计算结果是浮点数
print(10/3)
print(10//3)/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数还有一种除法是
//
,称为地板除,两个整数的除法仍然是整数%为取余计算
10、Python中的is和==
==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等
is是比较引用地址是否相等
Python中,万物皆对象!
「每个对象包含3个属性,id,type,value」
「id就是对象地址,可以通过内置函数id()查看对象引用的地址。」
「type就是对象类型,可以通过内置函数type()查看对象的类型。」
「value就是对象的值。」
11、编码
Python 3版本中,字符串是以Unicode编码的
对于单个字符的编码,Python提供了
ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符:print('\u4e2d\u6587') #表示中文的意思
print(ord('a'))
print(chr(97))\u则代表unicode编码,是一个字符
Python对
bytes
类型的数据用带b
前缀的单引号或双引号表示print('ABC'.encode('ascii')) //b'ABC'
区分
'ABC'
和b'ABC'
,前者是str
,后者虽然内容显示得和前者一样,但bytes
的每个字符都只占用一个字节。>>> b'ABC'.decode('ascii')
'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'如果我们从网络或磁盘上读取了字节流,那么读到的数据就是
bytes
。要把bytes
变为str
,就需要用decode()
方法:>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'如果
bytes
中只有一小部分无效的字节,可以传入errors='ignore'
忽略错误的字节:>>> b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
'中'要计算
str
包含多少个字符,可以用len()
函数:>>> len('ABC')
3
>>> len('中文')
2由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
或者
#!/usr/bin/env python3
#coding=utf-8
12、Python3注释
# 这是一个注释 多行注释用三个单引号 「'''」 或者三个双引号 「"""」 将注释括起来
13、运算符
逻辑运算符
运算符 逻辑表达式 描述 实例 and x and y 布尔"与" - 如果 x 为 False,x and y 返回 False,否则它返回 y 的计算值。 (a and b) 返回 20。 or x or y 布尔"或" - 如果 x 是 True,它返回 x 的值,否则它返回 y 的计算值。 (a or b) 返回 10。 not not x 布尔"非" - 如果 x 为 True,返回 False 。如果 x 为 False,它返回 True。 not(a and b) 返回 False 位运算符
& 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 (a & b) 输出结果 12 ,二进制解释:0000 1100 | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 (a | b) 输出结果 61 ,二进制解释:0011 1101 ^ 按位异或运算符:当两对应的二进位相异时,结果为1 (a ^ b) 输出结果 49 ,二进制解释:0011 0001 ~ 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。「~x」 类似于 「-x-1」 (~a ) 输出结果 -61 ,二进制解释:1100 0011, 在一个有符号二进制数的补码形式。 << 左移动运算符:运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。 a << 2 输出结果 240 ,二进制解释:1111 0000 >> 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数 a >> 2 输出结果 15 ,二进制解释:0000 1111 赋值运算符
运算符 描述 实例 = 简单的赋值运算符 c = a + b 将 a + b 的运算结果赋值为 c += 加法赋值运算符 c += a 等效于 c = c + a -= 减法赋值运算符 c -= a 等效于 c = c - a *= 乘法赋值运算符 c *= a 等效于 c = c * a /= 除法赋值运算符 c /= a 等效于 c = c / a %= 取模赋值运算符 c %= a 等效于 c = c % a **= 幂赋值运算符 c **= a 等效于 c = c ** a //= 取整除赋值运算符 c //= a 等效于 c = c // a := 海象运算符,可在表达式内部为变量赋值。「Python3.8 版本新增运算符」。 在这个示例中,赋值表达式可以避免调用 len() 两次: if (n := len(a)) > 10: print(f"List is too long ({n} elements, expected <= 10)")
算术运算符
运算符 描述 实例 + 加 - 两个对象相加 a + b 输出结果 31 - 减 - 得到负数或是一个数减去另一个数 a - b 输出结果 -11 * 乘 - 两个数相乘或是返回一个被重复若干次的字符串 a * b 输出结果 210 / 除 - x 除以 y b / a 输出结果 2.1 % 取模 - 返回除法的余数 b % a 输出结果 1 ** 幂 - 返回x的y次幂 a**b 为10的21次方 // 取整除 - 向下取接近商的整数 >>> 9//2 4 >>> -9//2 -5
比较运算符
运算符 描述 实例 == 等于 - 比较对象是否相等 (a == b) 返回 False。 != 不等于 - 比较两个对象是否不相等 (a != b) 返回 True。 > 大于 - 返回x是否大于y (a > b) 返回 False。 < 小于 - 返回x是否小于y。所有比较运算符返回1表示真,返回0表示假。这分别与特殊的变量True和False等价。注意,这些变量名的大写。 (a < b) 返回 True。 >= 大于等于 - 返回x是否大于等于y。 (a >= b) 返回 False。 <= 小于等于 - 返回x是否小于等于y。 (a <= b) 返回 True。 身份运算符
运算符 描述 实例 is is 是判断两个标识符是不是引用自一个对象 「x is y」, 类似 「id(x) == id(y)」 , 如果引用的是同一个对象则返回 True,否则返回 False is not is not 是判断两个标识符是不是引用自不同对象 「x is not y」 , 类似 「id(a) != id(b)」。如果引用的不是同一个对象则返回结果 True,否则返回 False。 成员运算符
运算符 描述 实例 in 如果在指定的序列中找到值返回 True,否则返回 False。 x 在 y 序列中 , 如果 x 在 y 序列中返回 True。 not in 如果在指定的序列中没有找到值返回 True,否则返回 False。 x 不在 y 序列中 , 如果 x 不在 y 序列中返回 True。
14、数字类型转换
「int(x)」 将x转换为一个整数。 「float(x)」 将x转换到一个浮点数。
15、list-列表
序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 - 它的位置,或索引,第一个索引是0,第二个索引是1,依此类推。
Python有6个序列的内置类型,但最常见的是列表和元组。
序列都可以进行的操作包括索引,切片,加,乘,检查成员。
此外,Python已经内置确定序列的长度以及确定最大和最小的元素的方法。
「列表的数据项不需要具有相同的类型」
list是一种有序的集合,可以随时添加和删除其中的元素。
访问列表 可正负
list1=['1','zz','333','444']
print(list1[0])
print(list1[-1])list是一个可变的有序表,所以,可以往list中追加元素到末尾:
list1.append("zzdc")
list1.append("zdc2")
print(list1)也可以把元素插入到指定的位置,比如索引号为
1
的位置list1.insert(1,222)
print(list1)要删除list末尾的元素,用
pop()
或pop(i)方法 pop会把删去的元素返回print(list1.pop())
print(list1.pop(0))要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
list1[0]='mzd'
print(list1)「list元素也可以是另一个list」
list1[0]=['1',2,3,4]
print(list1)>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']列表脚本操作符
for var in list1:
print(var)Python 表达式 结果 描述 len([1, 2, 3]) 3 长度 [1, 2, 3] + [4, 5, 6] [1, 2, 3, 4, 5, 6] 组合 ['Hi!'] * 4 ['Hi!', 'Hi!', 'Hi!', 'Hi!'] 重复 3 in [1, 2, 3] True 元素是否存在于列表中 for x in [1, 2, 3]: print(x, end=" ") 1 2 3 迭代 遍历
列表截取与拼接
操作:
Python 表达式 结果 描述 L[2] 'Taobao' 读取第三个元素 L[-2] 'Runoob' 从右侧开始读取倒数第二个元素: count from the right L[1:] ['Runoob', 'Taobao'] 输出从第二个元素开始后的所有元素 变量[头下标:尾下标]
变量[头下标:尾下标:步长]
list=['123','abc',0,True]
x=list[1:]
y=list[:3]
z=list[2:3]
print(x)
print(y)
print
list=['123','abc',0,True]
x=list[-3:]
y=list[:-2]
z=list[-3:-1]
print(x)
print(y)
print(z)
list=['123','abc',0,True,"12345"]
x=list[1:4:2]
print(x)列表函数&方法
Python包含以下函数:
序号 函数 1 len(list) 列表元素个数 2 max(list) 返回列表元素最大值 3 min(list) 返回列表元素最小值 4 list(seq) 将元组转换为列表 Python包含以下方法:
序号 方法 1 list.append(obj) 在列表末尾添加新的对象 2 list.count(obj) 统计某个元素在列表中出现的次数 3 list.extend(seq) 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表) 4 list.index(obj) 从列表中找出某个值第一个匹配项的索引位置 5 list.insert(index, obj) 将对象插入列表 6 [list.pop(index=-1]) 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 7 list.remove(obj) 移除列表中某个值的第一个匹配项 8 list.reverse() 反向列表中元素 9 list.sort( key=None, reverse=False) 对原列表进行排序 10 list.clear() 清空列表 11 list.copy() 复制列表
16、元组-tuple
tuple和list非常类似,但是「tuple一旦初始化就不能修改」
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来
t = (1, 2)
t = () #如果要定义一个空的tuple要定义一个只有1个元素的tuple,如果你这么定义
>>> t = (1)
>>> t
1定义的不是tuple,是
1
这个数!这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
。所以,「只有1个元素的tuple定义时必须加一个逗号
,
」,来消除歧义:>>> t = (1,)
>>> t
(1,)一个“可变的”tuple:
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])这个tuple定义的时候有3个元素,分别是
'a'
,'b'
和一个list。不是说tuple一旦定义后就不可变了吗?怎么后来又变了?[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT70crDv-1612773531303)(1.assets/0.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qusc6dYV-1612773531305)(1.assets/0-20200713003324698.png)]
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即指向'a'
,就不能改成指向'b'
,指向一个list,就不能改成指向其他对象,「但指向的这个list本身是可变的!」
「要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变」
以对元组进行连接组合
tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')
# 以下修改元组元素操作是非法的。
# tup1[0] = 100
# 创建一个新的元组
tup3 = tup1 + tup2
17、if else
if 判断条件:
执行语句……
else:
执行语句……当判断条件为多个值时
if 判断条件1:
执行语句1……
elif 判断条件2:
执行语句2……
elif 判断条件3:
执行语句3……
else:
执行语句4……if
判断条件还可以简写,比如写:if x:
print('True')只要
x
是非零数值、非空字符串、非空list等,就判断为True
,否则为False
。再议 input
最后看一个有问题的条件判断。很多同学会用
input()
读取用户的输入,这样可以自己输入,程序运行得更有意思:birth = input('birth: ')
if birth < 2000:
print('00前')
else:
print('00后')输入
1982
,结果报错:Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()这是因为
input()
返回的数据类型是str
,str
不能直接和整数比较,必须先把str
转换成整数。「Python提供了int()
函数来完成这件事情:」s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00后')再次运行,就可以得到正确地结果。但是,如果输入
abc
呢?又会得到一个错误信息:Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'abc'原来
int()
函数发现一个字符串并不是合法的数字时就会报错,程序就退出了。
18、循环
while 判断条件(condition):
执行语句(statements)……
「循环使用 else 语句」
在 python 中,while … else 在循环条件为 false 时执行 else 语句块:
count = 0
while count < 5:
print count, " is less than 5"
count = count + 1
else:
print count, " is not less than 5"
for 「循环语句」
for iterating_var in sequence:
statements(s)
在 python 中,for … else 表示这样的意思,for 中的语句和普通的没有区别,「else 中的语句会在循环正常执行完(即 for 不是通过 break 跳出而中断的)的情况下执行」,while … else 也是一样。
「Python 语言允许在一个循环体里面嵌入另一个循环。」
for iterating_var in sequence:
for iterating_var in sequence:
statements(s)
statements(s)
while expression:
while expression:
statement(s)
statement(s)
19、break、continue和pass
Python continue 语句跳出本次循环,而break跳出整个循环。
Python pass 是空语句,是为了保持程序结构的完整性。
「pass」 不做任何事情,一般用做占位语句。
在 Python3.x 的时候 pass 可以写或不写。
python2.x:
def function():
# 空函数在Python2.x版本中pass是必须的
passpython3.x
def function():
# 在Python3.x的时候pass可以写或不写
pass
20、Dictionary--字典
字典是另一种可变容器模型,且可存储任意类型对象。
字典的每个键值 「key=>value」 对用冒号 「:」 分割,每个键值对之间用逗号 「,」 分割,整个字典包括在花括号 「{}」 中 ,格式如下所示:
d = {key1 : value1, key2 : value2 }
如果key不存在,dict就会报错:
>>> d['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'要避免key不存在的错误,有两种办法,一是通过
in
判断key是否存在:>>> 'Thomas' in d
False二是通过dict提供的
get()
方法,如果key不存在,可以返回None
,或者自己指定的value:>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1注意:返回
None
的时候Python的交互环境不显示结果。要删除一个key,用
pop(key)
方法,对应的value也会从dict中删除:>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。
和list比较,dict有以下几个特点:
而list相反:
所以,dict是用空间来换取时间的一种方法。
「这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。」
「要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:」
查找和插入的时间随着元素的增加而增加; 占用空间小,浪费内存很少。
查找和插入的速度极快,不会随着key的增加而变慢; 需要占用大量的内存,内存浪费多。
21、函数
定义函数
在Python中,定义一个函数要使用
def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。def my_abs(x):
if x >= 0:
return x
else:
return -x「如果你已经把
my_abs()
的函数定义保存为abstest.py
文件了,那么,可以在该文件的当前目录下启动Python解释器,用from abstest import my_abs
来导入my_abs()
函数,注意abstest
是文件名(不含.py
扩展名):」参数的可变与不可变
**不可变类型:**变量赋值 「a=5」 后再赋值 「a=10」,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变a的值,相当于新生成了a。 **可变类型:**变量赋值 「la=[1,2,3,4]」 后再赋值 「la[2]=5」 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。 **不可变类型:**类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。 **可变类型:**类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响 在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
python 函数的参数传递:
「python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。」
python 传不可变对象实例
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def ChangeInt( a ):
a = 10
b = 2
ChangeInt(b)
print b # 结果是 2传可变对象实例
!/usr/bin/python
# -*- coding: UTF-8 -*-
# 可写函数说明
def changeme( mylist ):
"修改传入的列表"
mylist.append([1,2,3,4])
print "函数内取值: ", mylist
return
# 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print "函数外取值: ", mylist命名关键字参数
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#可写函数说明
def printme( str ):
"打印任何传入的字符串"
print str
return
#调用printme函数
printme( str = "My string")关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。
「使用关键字参数允许函数调用时参数的顺序与声明时不一致」,因为 Python 解释器能够用参数名匹配参数值
关键字参数
**可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。**请看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)函数
person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数:>>> person('Michael', 30)
name: Michael age: 30 other: {}也可以传入任意个数的关键字参数:
>>> person('Bob', 35, city='Beijing')
name: Bob age: 35 other: {'city': 'Beijing'}
>>> person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}关键字参数有什么用?它可以扩展函数的功能。比如,在
person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}当然,上面复杂的调用可以用简化的写法:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过
kw
检查。仍以
person()
函数为例,我们希望检查是否有city
和job
参数:def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)但是调用者仍可以传入不受限制的关键字参数:
>>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
**如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收
city
和job
作为关键字参数。**这种方式定义的函数如下:def person(name, age, *, city, job):
print(name, age, city, job)「和关键字参数
**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。」调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
*
了:def person(name, age, *args, city, job):
print(name, age, args, city, job)命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given由于调用时缺少参数名
city
和job
,Python解释器把这4个参数均视为位置参数,但person()
函数仅接受2个位置参数。命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)由于命名关键字参数
city
具有默认值,调用时,可不传入city
参数:>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个
*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数:def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass默认参数
调用函数时,默认参数的值如果没有传入,则被认为是默认值。下例会打印默认的age,如果age没有被传入:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
#可写函数说明
def printinfo( name, age = 35 ):
"打印任何传入的字符串"
print "Name: ", name
print "Age ", age
return
#调用printinfo函数
printinfo( age=50, name="miki" )
printinfo( name="miki" )「定义默认参数要牢记一点:默认参数必须指向不变对象!」
def add_end(L=[]):
L.append('END')
return L
>>> add_end()
['END']
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']很多初学者很疑惑,默认参数是
[]
,但是函数似乎每次都“记住了”上次添加了'END'
后的list。原因解释如下:
Python函数在定义的时候,默认参数
L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。可变参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum但是调用的时候,需要先组装出一个list或tuple:
>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84如果利用可变参数,调用函数的方式可以简化成这样:
>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84所以,我们把函数的参数改为可变参数:
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum定义可变参数和定义一个list或tuple参数相比,「仅仅在参数前面加了一个
*
号」。在函数内部,参数numbers
接收到的是一个tuple,因此,函数代码完全不变。「但是,调用该函数时,可以传入任意个参数,包括0个参数」:>>> calc(1, 2)
5
>>> calc()
0如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14这种写法当然是可行的,问题是太繁琐,「所以Python允许你在list或tuple前面加一个
*
号,把list或tuple的元素变成可变参数传进去」:>>> nums = [1, 2, 3]
>>> calc(*nums)
14「
*nums
表示把nums
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。」def change(*numbers):
sum=0;
for i in numbers:
sum+=i;
return sum;
print(change(1,2,3))
st=(1,2,3,4,5,6,7)
print(change(*st))在Python中定义函数,可以用必选参数、默认参数、可变参数和关键字参数,这4种参数都可以一起使用,或者只用其中某些,但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数和关键字参数。
def func(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>>> func(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}匿名函数
lambda只是一个表达式,函数体比def简单很多。 lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。 lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。 python 使用 lambda 来创建匿名函数。
语法
lambda函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
如下实例:
实例(Python 2.0+)
#!/usr/bin/python # -- coding: UTF-8 -- # 可写函数说明 sum = lambda arg1, arg2: arg1 + arg2 # 调用sum函数 print "相加后的值为 : ", sum( 10, 20 ) print "相加后的值为 : ", sum( 20, 20 )
以上实例输出结果:
相加后的值为 : 30
相加后的值为 : 40返回多个值
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
x, y = move(100, 100, 60, math.pi / 6)全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
实例(Python 2.0+)
#!/usr/bin/python
# -*- coding: UTF-8 -*-
total = 0 # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
#返回2个参数的和."
total = arg1 + arg2 # total在这里是局部变量.
print "函数内是局部变量 : ", total
return total
#调用sum函数
sum( 10, 20 )
print "函数外是全局变量 : ", total「默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!」
「要注意定义可变参数和关键字参数的语法:」
「
*args
是可变参数,args接收的是一个tuple;」kw
是关键字参数,kw接收的是一个dict。「以及调用函数时如何传入可变参数和关键字参数的语法:」
「可变参数既可以直接传入:
func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
;」「关键字参数既可以直接传入:
func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。」**使用
*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。「命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。」
「定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数。」函数别名
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1Import 与 from import
>>> import datetime
>>> print(datetime.datetime.now()
>>> from datetime import datetime
>>> print(datetime.now())包的概念 packa包中
from packa.a import a
from packa.b import b
a()
b()
22、切片
lis=[1,2,3,4,5,6,7,8]
print(lis[0:3])
print(lis[:4])
print(lis[-5:-3])
print(lis[-5:])
23、迭代
在Python中,迭代是通过
for ... in
来完成的for num in lis:
print(num)默认情况下,dict迭代的是key。如果要迭代value,可以用
for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
d = {'a': 1, 'b': 2, 'c': 3}
for k in d:
print(k)
for v in d.values():
print(v)
for k,v in d.items():
print('k=',k,'v=',v)字符串也是可迭代对象
for ch in 'ABCD':
print(ch)如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False如果要对list实现类似Java那样的下标循环怎么办?Python内置的
enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:for i,v in enumerate(lis):
print(i,v)
24、列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
python range() 函数可创建一个整数列表,一般用在 for 循环中。
print(list(range(1,20)))
l=[]
for i in range(1,20):
l.append(i*i)
print(l)
print([x * x for x in range(1,5)]) #列表生成式
print([x * x for x in range(1, 10) if x%2==1])
print([d for d in os.listdir('/')]) #import os写列表生成式时,把要生成的元素
x * x
放到前面,后面跟for
循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。「for循环后面还可以加上if判断」。
「还可以使用两层循环,可以生成全排列」:
print([x+y for x in 'abc' for y in 'bdf'])
列表生产式也可以同时使用多个变量
d = {'x': 'A', 'y': 'B', 'z': 'C'}
print([k+'='+v for k,v in d.items()])列表生成式中的if....else
print([[x if x % 2 == 0 else -x for x in range(1, 11)]])
print([x if x>2 else 100 for x in range(1,5)])「在一个列表生成式中,
for
前面的if ... else
是表达式,而for
后面的if
是过滤条件,不能带else
。」
25、生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,「如果列表元素可以按照某种算法推算出来」,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的
[]
改成()
,就创建了一个generatorg=(x*x for x in range(10));
print(next(g))
print(next(g))
print(next(g))
for i in g:
print(i)generator保存的是算法,每次调用
next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。我们创建了一个generator后,基本上永远不会调用
next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的
for
循环无法实现的时候,「还可以用函数来实现」。def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator;「函数是顺序执行,遇到
return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。」def odd():
print('step 1')
yield 1
print('step 2')
yield(3)
print('step 3')
yield(5)但是用
for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
26、迭代器
一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等;一类是
generator
,包括生成器和带yield
的generator function。这些可以直接作用于
for
循环的对象统称为可迭代对象:Iterable
。可以使用
isinstance()
判断一个对象是否是Iterable
对象而生成器不但可以作用于
for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。可以被
next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。生成器都是
Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。把
list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True你可能会问,为什么
list
、dict
、str
等数据类型不是Iterator
?这是因为Python的
Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,「所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算」。Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。「凡是可作用于
for
循环的对象都是Iterable
类型;」「凡是可作用于
next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;」集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。「Python的
for
循环本质上就是通过不断调用next()
」
27、函数式编程
1、高阶函数
变量可以指向函数
函数名也是变量
既然变量可以指向函数,函数的参数能接收变量,那么「一个函数就可以接收另一个函数作为参数」,这种函数就称之为「高阶函数」
f=abs
print(f)
abs=10 #abs本来是内嵌函数
print(abs)
def absAdd(x,y,f):
return f(x)+f(y)
print(absAdd(-1,-2,f))
2、map/reduce
map
def pow(x):
return x*x
# 返回的是一个Iterator 因此通过list()函数让它把整个序列都计算出来并返回一个list。
lis = map(pow, [1, 2, 3, 4, 5])
print(list(lis))
print(list(map(str,[1,2,3,4,5]))) #返回字符串reduce
from functools import reduce
def add(x,y):
return x+y
print(reduce(add,[1,2,3,4,5]))reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,re**duce
把结果继续和序列的下一个元素做累积计算**,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
filter
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]Python内建的
filter()
函数用于过滤序列。和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。sorted
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]此外,
sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
3、返回函数
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
print(f())当我们调用
lazy_sum()
时,返回的并不是求和结果,而是求和函数:「在函数
lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。」
闭包
当一个函数返回了一个函数后,其内部的局部变量还被新函数引用
返回的函数并没有立刻执行,而是直到调用了
f()
才执行def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用f1(),f2()和f3()结果应该是1,4,9,但实际结果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()在上面的例子中,每次循环,都创建了一个新的函数,然后,把创建的3个函数都返回了。
你可能认为调用
f1()
,f2()
和f3()
结果应该是1
,4
,9
,但实际结果是:>>> f1()
9
>>> f2()
9
>>> f3()
9全部都是
9
!原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3
,因此最终结果为9
。「返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。」
4、lambda
f=lambda x:x*x-2;
f1=lambda x,y:x+y
print(f(2))
print(f1(3,4))
5、装饰器
def log(f):
def decrotor(*args,**kw):
print('call %s():' % f.__name__)
return f(*args, **kw)
return decrotor
@log
def tes():
print('tes')
tes()把
@log
放到now()
函数的定义处,相当于执行了语句:now = log(now)
由于
log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')执行结果如下:
>>> now()
execute now():
2015-3-25和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行
log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有
__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:>>> now.__name__
'wrapper'因为返回的那个
wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。不需要编写
wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decoratorimport functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
6、偏函数
Python的
functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。nt()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:>>> int('12345')
12345但
int()
函数还提供额外的base
参数,默认值为10
。如果传入base
参数,就可以做N进制的转换:>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565假设要转换大量的二进制字符串,每次都传入
int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:def int2(x, base=2):
return int(x, base)这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85所以,简单总结
functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。注意到上面的新的
int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值:>>> int2('1000000', base=10)
1000000最后,创建偏函数时,实际上可以接收函数对象、
*args
和**kw
这3个参数,当传入:int2 = functools.partial(int, base=2)
实际上固定了int()函数的关键字参数
base
,也就是:int2('10010')
相当于:
kw = { 'base': 2 }
int('10010', **kw)当传入:
max2 = functools.partial(max, 10)
实际上会把
10
作为*args
的一部分自动加到左边,也就是:max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)结果为
10
。
28、模块
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行和第2行是标准注释,第1行注释可以让这个hello.py
文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。
你可能注意到了,使用sys
模块的第一步,就是导入该模块:
import sys
导入sys
模块后,我们就有了变量sys
指向该模块,利用sys
这个变量,就可以访问sys
模块的所有功能。
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是['hello.py']
;
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py', 'Michael']
。
最后,注意到这两行代码:
if __name__=='__main__':
test()
**当我们在命令行运行hello
模块文件时,Python解释器把一个特殊变量__name__
置为__main__
,**而如果在其他地方导入该hello
模块时,if
判断将失败,因此,这种if
测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。
私有变量和函数
正常的函数和变量名是公开的(public),可以被直接引用,比如:
abc
,x123
,PI
等;「类似
__xxx__
这样的变量是特殊变量」,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello
模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名;**类似
_xxx
和__xxx
**这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;类似
_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;「之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。」
在Python中,有以下几种方式来定义变量:
xx:公有变量 _xx:单前置下划线,私有化属性或方法,类对象和子类可以访问,from somemodule import *禁止导入 __xx:双前置下划线,私有化属性或方法,无法在外部直接访问(名字重整所以访问不到) 「xx」:双前后下划线,系统定义名字(不要自己发明这样的名字) xx_:单后置下划线,用于避免与Python关键词的冲突
「使用不同方法导入模块,模块中私有变量的使用区别」
「在使用不同方法导入模块后,是否能使用模块中的私有属性和方法,有以下两种情况」
在使用 from somemodule import * 导入模块的情况下,不能导入或使用私有属性和方法 在使用 import somemodule 导入模块的情况下,能导入并使用私有属性和方法
第三方模块
在Python中,安装第三方模块,是通过包管理工具pip完成的。
模块搜索路径
当我们试图加载一个模块时,Python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:
>>> import mymodule
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: No module named mymodule「默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在
sys
模块的path
变量中」:>>> import sys
>>> sys.path
['', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', ..., '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages']如果我们要添加自己的搜索目录,有两种方法:
一是直接修改
sys.path
,添加要搜索的目录:>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')这种方法是在运行时修改,运行结束后失效。
「第二种方法是设置环境变量
PYTHONPATH
,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。」
29、类和实例
类
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,紧接着是(object)
,表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去
注意到**__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。**
「有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去」
「和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。」
「可以自由地给一个实例变量绑定属性」
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'
方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
stu=Student('zdc',29)
def min(a,b):
if a>b:
return b
else:
return a
print(stu)
stu.min=min
print(stu.min(1,2))
stu.zz='ssd'
print(stu.zz)
「实例可以任意添加属性和方法」
30、访问限制
class Student(object):
def __init__(self,name,score):
self.__name=name
self.__score=score
stu=Student('zdc',29)
print(stu._Student__name)#可以访问
print(stu.__name)
如果要让内部属性不被外部访问,「可以把属性的名称前加上两个下划线__
」,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name
和实例变量.__score
了:
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name
和get_score
这样的方法:
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score
方法:
class Student(object):
...
def set_score(self, score):
self.__score = score
你也许会问,原先那种直接通过bart.score = 99
也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:
class Student(object):
...
def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')
需要注意的是,在Python中,「变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量」,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名
有些时候,你会看到以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。「不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量」:
31、继承和多态
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run()继承的第二个好处需要我们对代码做一点改进。你看到了,无论是
Dog
还是Cat
,它们run()
的时候,显示的都是Animal is running...
,符合逻辑的做法是分别显示Dog is running...
和Cat is running...
,因此,对Dog
和Cat
类改进如下:class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')再次运行,结果如下:
Dog is running...
Cat is running...当子类和父类都存在相同的
run()
方法时,我们说,子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。这样,我们就获得了继承的另一个好处:多态。def run_twice(animal):
animal.run()
32、获取对象信息
基本类型都可以用
type()
判断>>> type(123)
<class 'int'>
>>> type('str')
<class 'str'>
>>> type(None)
<type(None) 'NoneType'>如果一个变量指向函数或者类,也可以用
type()
判断:>>> type(abs)
<class 'builtin_function_or_method'>
>>> type(a)
<class '__main__.Animal'>但是
type()
函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if
语句中判断,就需要比较两个变量的type类型是否相同:>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False判断基本数据类型可以直接写
int
,str
等,但如果要判断一个对象是否是函数怎么办?可以使用types
模块中定义的常量:>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True使用isinstance()
对于class的继承关系来说,使用
type()
就很不方便。我们要判断class的类型,可以使用isinstance()
函数。isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:
>>> a = Animal()
>>> d = Dog()
>>> h = Husky()
然后,判断:
>>> isinstance(h, Husky)
True
>>> isinstance(h, Dog)
True #但由于Husky是从Dog继承下来的,所以,h也还是Dog类型dir()
如果要获得一个对象的所有属性和方法,可以使用
dir()
函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']仅仅把属性和方法列出来是不够的,配合**
getattr()
、setattr()
以及hasattr()
**,我们可以直接操作一个对象的状态:>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404也可以获得对象的方法:
>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81
33、实例属性和类属性
class Demo:
count=100; #类变量
def __init__(self,name,age):
self.__name=name
self.__age=age
d=Demo('zz',22)
d2=Demo('dd',11)
d.count=222 # 给实例绑定count属性 类的count被隐藏了
print(d2.count) #100 因为实例并没有count属性,所以会继续查找class的count属性
print(d.count) #222
print(Demo.count) #100
del d.count #删除d的实例属性 此时可以访问到类属性
print(d.count)「在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性」
33、__slots__限制实例绑定任何属性和方法
常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。但是,给一个实例绑定的方法,对另一个实例是不起作用的
但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加
name
和age
属性。为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的
__slots__
变量,来限制该class实例能添加的属性:class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'除非在子类中也定义
__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。「len」
__len__
如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。
要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:
class Students(object):
def __init__(self, *args):
self.names = args
def __len__(self):
return len(self.names)
只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:
34、@property @xxx.setter
class Person(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
p = Person()
p.birth = 100
# p.age=11 #age为只读
print(p.birth)
print(p.age)把一个getter方法变成属性,只需要加上
@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值
35、多继承
Python允许使用多重继承
36、定制类
「str」
我们先定义一个
Student
类,打印一个实例:>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>打印出一堆
<__main__.Student object at 0x109afb190>
,不好看。怎么才能打印得好看呢?只需要定义好
__str__()
方法,返回一个好看的字符串就可以了:>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
怎么说呢,python 的特性太胶水了,不适合作为项目主力,所以它是生产力技能而不是业务技能。
猜你喜欢
更多精彩,请戳"阅读原文"到"数仓之路"查看
!关注不迷路~ 各种干货、资源定期分享!
学习小密圈 点击☞加群